package com.flame.shapeville.controller.task;

import com.flame.shapeville.bean.Exercise;
import com.flame.shapeville.component.task3.*;
import com.flame.shapeville.util.Utils;
import javafx.collections.ObservableList;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.Region;
import javafx.scene.layout.StackPane;

import java.util.HashMap;
import java.util.Map;

/**
 * Controller for Task 3, which involves selecting a basic shape and calculating its area.
 * <p>
 * Users can choose from Rectangle, Parallelogram, Triangle, or Trapezium.
 * Each shape has its own set of parameters (e.g., width, height).
 *
 * @author Flame Dev Team
 * @version 1.0
 * @since 2025-05-13
 */
public class Task3Controller extends JfTaskDetailedController {
    private GridPane choicePane;
    private Map<Button, Exercise> choiceMap;
    private JfShape jfShape;

    /**
     * Initializes the controller by setting up UI components and loading the current exercise.
     */
    @Override
    public void init() {
        super.init();
        initChoiceBlock();
        setExercise();
        flushSwitchButton();
        initNotice();
        initInput();
    }

    /**
     * Sets up the shape selection buttons with appropriate graphics and event handlers.
     */
    private void initChoiceBlock() {
        choicePane = new GridPane();
        choicePane.setAlignment(Pos.CENTER);
        for (int i = 0; i < 4; i++) {
            Button button = new Button();
            button.setPrefSize(120, 50);
            button.setMaxSize(120, 50);
            choicePane.add(button, i % 2, i / 2);
            button.setOnAction(event -> {
                Button source = (Button) event.getSource();
                Exercise exercise = choiceMap.get(source);
                exercise.setNo(exPointer + 1);
                exercise.addParam("selected", true);
                exercises.remove(exercise);
                exercises.add(exPointer, exercise);
                setExercise();
            });
            switch (i) {
                case 0:
                    button.setGraphic(new JfRectangle(30, 30, 20).getGraphics());
                    break;
                case 1:
                    button.setGraphic(new JfParallelogram(30, 25, 20, 15).getGraphics());
                    break;
                case 2:
                    button.setGraphic(new JfTriangle(30, 30, 30).getGraphics());
                    break;
                case 3:
                    button.setGraphic(new JfTrapezium(30, 20, 30, 30).getGraphics());
                    break;
            }
        }
        choiceMap = new HashMap<>();
        for (Exercise exercise : exercises) {
            if (exercise.getParam("shape").equals("rectangle")) {
                choiceMap.put((Button) Utils.getNodeFromGridPane(choicePane, 0, 0), exercise);
            } else if (exercise.getParam("shape").equals("parallelogram")) {
                choiceMap.put((Button) Utils.getNodeFromGridPane(choicePane, 0, 1), exercise);
            } else if (exercise.getParam("shape").equals("triangle")) {
                choiceMap.put((Button) Utils.getNodeFromGridPane(choicePane, 1, 0), exercise);
            } else if (exercise.getParam("shape").equals("trapezium")) {
                choiceMap.put((Button) Utils.getNodeFromGridPane(choicePane, 1, 1), exercise);
            }
        }
    }

    /**
     * Sets up input validation and event listeners for user answer submission.
     */
    private void initInput() {
        input.setOnKeyPressed(event -> {
            if (event.getCode() == javafx.scene.input.KeyCode.ENTER) {
                submit();
            }
        });
        input.textProperty().addListener((observable, oldValue, newValue) -> {
            if (newValue.matches("\\d*")) {
                input.setText(newValue);
            }
        });
        Utils.addClickOutsideToLoseFocus(input, event -> input.getParent().requestFocus());
    }

    /**
     * Sets the current exercise based on pointer index and updates the UI accordingly.
     */
    @Override
    void setExercise() {
        curExercise = exercises.get(exPointer);
        if (!(Boolean) curExercise.getParam("selected")) {
            question.setText("Choice a shape you want to calculate");
            no.setText((exPointer + 1) + "");
            loadChoice();
        } else {
            question.setText("Calculate the area of " + curExercise.getParam("shape"));
            no.setText(curExercise.getNo() + "");
            loadShape();
            if (!curExercise.getCompleted()) {
                startClock();
            }
        }
        flushInput();
    }

    /**
     * Displays the shape selection pane and disables already selected options.
     */
    private void loadChoice() {
        shape.getChildren().clear();
        Region region = new Region();
        region.setPrefHeight(50);
        shape.getChildren().addAll(region, choicePane);
        for (Map.Entry<Button, Exercise> entry : choiceMap.entrySet()) {
            if ((Boolean) entry.getValue().getParam("selected")) {
                entry.getKey().setDisable(true);
            }
        }
    }

    /**
     * Loads the selected shape into the UI along with its parameters.
     */
    private void loadShape() {
        ObservableList<Node> nodes = shape.getChildren();
        nodes.clear();
        Label label = new Label();
        label.getStyleClass().add("sub-question");

        switch ((String) curExercise.getParam("shape")) {
            case "rectangle":
                label.setText("width is " + curExercise.getParam("width") + " and height is " + curExercise.getParam("height"));
                jfShape = new JfRectangle(150, (Integer) curExercise.getParam("width"), (Integer) curExercise.getParam("height"));
                break;
            case "parallelogram":
                label.setText("base is " + curExercise.getParam("base") + " and height is " + curExercise.getParam("height"));
                jfShape = new JfParallelogram(150, (Integer) curExercise.getParam("base"), (Integer) curExercise.getParam("height"));
                break;
            case "triangle":
                label.setText("base is " + curExercise.getParam("base") + " and height is " + curExercise.getParam("height"));
                jfShape = new JfTriangle(150, (Integer) curExercise.getParam("base"), (Integer) curExercise.getParam("height"));
                break;
            case "trapezium":
                label.setText("top is " + curExercise.getParam("top") + " and base is " + curExercise.getParam("base") + " and height is " + curExercise.getParam("height"));
                jfShape = new JfTrapezium(150, (Integer) curExercise.getParam("top"), (Integer) curExercise.getParam("base"), (Integer) curExercise.getParam("height"));
                break;
        }
        if (!curExercise.getCompleted()) {
            StackPane shapePane = new StackPane();
            shapePane.getChildren().add(jfShape.getShape());
            nodes.addAll(label, shapePane);
        } else {
            nodes.add(jfShape.getDetailedShape());
        }
    }

    /**
     * Handles user submission of an answer.
     */
    @Override
    void submit() {
        if (curExercise.getAnswer() == null) {
            return;
        }
        String answer = input.getText();
        if (answer == null || answer.isEmpty()) {
            return;
        }
        answerAnimationAndSave(answer);
    }

    /**
     * Updates input field state based on whether the current exercise is completed.
     */
    @Override
    void flushInput() {
        if (curExercise.getCompleted()) {
            input.setText(curExercise.getAnswer());
            input.setDisable(true);
            submit.setDisable(true);
        } else {
            input.clear();
            input.setDisable(false);
            submit.setDisable(false);
        }
    }

    /**
     * Shows detailed explanation of the shape after timeout or incorrect answer.
     */
    @Override
    void showDetailed() {
        shape.getChildren().clear();
        shape.getChildren().add(jfShape.getDetailedShape());
        jfShape.translateAnimation();
    }
}
